/*!
 * \file ParserYD.cpp
 * \project	WonderTrader
 *
 * \author Wesley
 * \date 2020/03/30
 * 
 * \brief YD行情解析器实现文件
 * 
 * \details 该文件实现了ParserYD类的所有功能，主要包括：
 *          - YD行情API的完整封装和管理
 *          - 期货行情数据的实时接收和处理
 *          - 基于配置文件的参数管理方式
 *          - YD连接状态监控和错误处理
 *          - 行情数据格式转换（YD -> WonderTrader标准格式）
 *          - 异步登录和初始化处理机制
 *          - 动态库加载和API版本兼容性处理
 *          - 完整的日志记录和调试支持
 *          - 期货合约订阅和数据推送管理
 *          - YD特有的API初始化状态跟踪
 * 
 * \note 该实现专门针对YD（易达）系统的期货行情接入
 * \warning 使用前必须确保YD动态库正确部署和配置文件有效
 */
#include "ParserYD.h"

#include "../Includes/WTSDataDef.hpp"
#include "../Includes/WTSContractInfo.hpp"
#include "../Includes/WTSVariant.hpp"
#include "../Includes/IBaseDataMgr.h"

#include "../Share/ModuleHelper.hpp"
#include "../Share/TimeUtils.hpp"
#include "../Share/StdUtils.hpp"

#include <boost/filesystem.hpp>

/*!
 * \brief 通用日志记录模板函数
 * \tparam Args 可变参数类型
 * \param sink 行情回调接口指针
 * \param ll 日志级别
 * \param format 格式字符串
 * \param args 可变参数列表
 * 
 * \details 提供格式化日志输出功能，支持：
 *          - 多种日志级别（INFO、ERROR、WARN、DEBUG）
 *          - 线程安全的格式化输出
 *          - 自动内存管理和缓冲区保护
 *          - 与WonderTrader日志系统集成
 * 
 * \note 使用fmtlib提供高性能格式化，线程本地存储确保安全性
 */
 //By Wesley @ 2022.01.05
#include "../Share/fmtlib.h"
template<typename... Args>
inline void write_log(IParserSpi* sink, WTSLogLevel ll, const char* format, const Args&... args)
{
	if (sink == NULL)
		return;

	static thread_local char buffer[512] = { 0 };
	memset(buffer, 0, 512);
	fmt::format_to(buffer, format, args...);

	sink->handleParserLog(ll, buffer);
}

/*!
 * \brief C接口导出函数
 * 
 * 提供标准的动态库导出接口，支持插件化架构
 */
extern "C"
{
	/*!
	 * \brief 创建YD行情解析器实例
	 * \return 新创建的行情解析器接口指针
	 * 
	 * 用于动态库插件系统，创建ParserYD实例
	 */
	EXPORT_FLAG IParserApi* createParser()
	{
		ParserYD* parser = new ParserYD();
		return parser;
	}

	/*!
	 * \brief 销毁YD行情解析器实例
	 * \param parser 要销毁的行情解析器接口指针（引用传递，销毁后置为NULL）
	 * 
	 * 安全地销毁ParserYD实例并清理资源
	 */
	EXPORT_FLAG void deleteParser(IParserApi* &parser)
	{
		if (NULL != parser)
		{
			delete parser;
			parser = NULL;
		}
	}
};

/*!
 * \brief 构造函数
 * 
 * 初始化ParserYD实例的各个成员变量为默认值
 */
ParserYD::ParserYD()
	: m_pUserAPI(NULL)
	, m_uTradingDate(0)
	, m_bApiInited(false)
{
}

/*!
 * \brief 析构函数
 * 
 * 清理ParserYD实例，释放相关资源
 */
ParserYD::~ParserYD()
{
	m_pUserAPI = NULL;
}

/*!
 * \brief 准备登录通知回调实现
 * \param hasLoginFailed 是否曾经登录失败
 * 
 * \details 当YD API准备好进行登录时的处理：
 *          - 记录连接成功信息
 *          - 触发WPE_Connect事件通知上层应用
 *          - 自动调用DoLogin()开始登录流程
 * 
 * \note 这是YD连接的第一个回调，表示网络连接已建立
 */
void ParserYD::notifyReadyForLogin(bool hasLoginFailed)
{
	if (m_sink)
	{
		write_log(m_sink, LL_INFO, "[ParserYD] Market data server connected");
		m_sink->handleEvent(WPE_Connect, 0);
	}

	DoLogin();
}

/*!
 * \brief 登录结果通知回调实现
 * \param errorNo 错误代码（0表示成功）
 * \param maxOrderRef 最大订单引用号
 * \param isMonitor 是否为监控模式
 * 
 * \details 处理YD用户登录结果：
 *          - 登录成功时：记录成功信息，获取交易日期，触发WPE_Login事件
 *          - 登录失败时：记录错误代码和失败信息
 *          - 根据API初始化状态决定是否立即开始行情订阅
 *          - 支持监控模式和交易模式的区分处理
 * 
 * \note 如果API已初始化完成，登录成功后会立即调用DoSubscribe()
 */
void ParserYD::notifyLogin(int errorNo, int maxOrderRef, bool isMonitor)
{
	if (errorNo == 0)
	{
		write_log(m_sink, LL_INFO, "[ParserYD] {} login successfully", m_strUserID);

		m_uTradingDate = m_pUserAPI->getTradingDay();
		if (m_sink)
		{
			m_sink->handleEvent(WPE_Login, 0);
		}

		// 如果API已初始化完成，则直接订阅
		// 否则等待初始化完成回调
		if (m_bApiInited)
		{
			// 开始订阅行情数据
			DoSubscribe();
		}
	}
	else
	{
		write_log(m_sink, LL_INFO, "[ParserYD] {} login failed, error no: {}", m_strUserID, errorNo);
	}
}

/*!
 * \brief API初始化完成通知回调实现
 * 
 * \details 当YD API完全初始化完成时的处理：
 *          - 记录初始化完成信息
 *          - 设置API初始化完成标志
 *          - 如果是首次初始化，开始行情数据订阅
 *          - 确保所有合约信息已加载完毕
 * 
 * \note 这是YD连接流程的最后一步，之后可以正常进行行情订阅
 */
void ParserYD::notifyFinishInit(void)
{
	if (m_sink) m_sink->handleParserLog(LL_INFO, "[ParserYD] All things ready");

	// 如果API没有初始化完成，说明这个回调是第一次调用
	// 需要等待一些其他的等待操作
	if(!m_bApiInited)
	{
		// 开始订阅行情数据
		DoSubscribe();
		m_bApiInited = true;
	}
}

/*!
 * \brief 行情数据通知回调实现
 * \param pDepthMarketData YD行情数据结构指针
 * 
 * \details 这是最核心的期货行情数据处理方法，功能包括：
 *          - 期货合约信息验证：检查合约是否存在于基础数据中
 *          - 交易所识别：从YD合约信息中获取交易所代码
 *          - 时间处理：解析YD时间戳为日期和时间
 *          - 数据转换：将YD行情数据转换为WonderTrader标准tick格式
 *          - 价格数据：处理最新价、涨跌停价、昨收价、昨结算价
 *          - 成交数据：处理成交量、成交额、持仓量
 *          - 盘口数据：处理买卖一档价格和数量
 *          - 期货特有字段：处理结算价、持仓量等期货专用数据
 * 
 * \note 支持期货行情数据的完整处理，包含期货特有的持仓量等字段
 * \warning 必须确保m_pBaseDataMgr有效，否则无法处理行情数据
 */
void ParserYD::notifyMarketData(const YDMarketData *pDepthMarketData)
{
	if (m_pBaseDataMgr == NULL)
		return;

	const YDInstrument* instInfo = pDepthMarketData->m_pInstrument;
	const YDExchange* exchgInfo = instInfo->m_pExchange;
	uint32_t actDate = pDepthMarketData->TradingDay;
	uint32_t actTime = pDepthMarketData->TimeStamp;
	uint32_t actHour = actTime / 10000000;

	WTSContractInfo* contract = m_pBaseDataMgr->getContract(instInfo->InstrumentID, exchgInfo->ExchangeID);
	if (contract == NULL)
		return;

	WTSTickData* tick = WTSTickData::create(instInfo->InstrumentID);
	WTSTickStruct& quote = tick->getTickStruct();
	wt_strcpy(quote.exchg, contract->getExchg());
	tick->setContractInfo(contract);

	quote.action_date = actDate;
	quote.action_time = actTime;

	quote.price = pDepthMarketData->LastPrice;
	quote.total_volume = pDepthMarketData->Volume;
	quote.trading_date = m_uTradingDate;
	quote.total_turnover = pDepthMarketData->Turnover;

	quote.open_interest = pDepthMarketData->OpenInterest;

	quote.upper_limit = pDepthMarketData->UpperLimitPrice;
	quote.lower_limit = pDepthMarketData->LowerLimitPrice;

	quote.pre_close = pDepthMarketData->PreClosePrice;
	quote.pre_settle = pDepthMarketData->PreSettlementPrice;
	quote.pre_interest = pDepthMarketData->PreOpenInterest;

	// 委托卖价格
	quote.ask_prices[0] = pDepthMarketData->AskPrice;
	// 委托买价格
	quote.bid_prices[0] = pDepthMarketData->BidPrice;
	// 委托卖量
	quote.ask_qty[0] = pDepthMarketData->AskVolume;
	// 委托买量
	quote.bid_qty[0] = pDepthMarketData->BidVolume;

	if (m_sink)
		m_sink->handleQuote(tick, 1);

	tick->release();
}

/*!
 * \brief 初始化YD行情解析器
 * \param config 配置参数对象
 * \return 是否初始化成功
 * 
 * \details 从配置文件中读取YD连接参数并初始化API：
 *          - 配置文件路径：config(YD配置文件路径)
 *          - 用户认证信息：user(用户名)、pass(密码)
 *          - 动态库模块：ydmodule(YD动态库模块名，默认：yd)
 *          - 动态库加载：根据模块名加载对应的YD API动态库
 *          - API创建：通过makeYDApi函数创建YD API实例
 *          - 配置文件传递：将配置文件路径传递给YD API
 * 
 * \note YD使用配置文件方式管理连接参数，与其他解析器的参数方式不同
 * \warning 必须确保YD配置文件存在且格式正确，动态库文件可访问
 */
bool ParserYD::init(WTSVariant* config)
{
	m_strCfgFile = config->getCString("config");
	m_strUserID = config->getCString("user");
	m_strPassword = config->getCString("pass");

	std::string module = config->getCString("ydmodule");
	if (module.empty())
		module = "yd";

	std::string dllpath = getBinDir() + DLLHelper::wrap_module(module.c_str(), "");
	m_hInstYD = DLLHelper::load_library(dllpath.c_str());	
	m_funcCreator = (YDCreator)DLLHelper::get_symbol(m_hInstYD, "makeYDApi");
	m_pUserAPI = m_funcCreator(m_strCfgFile.c_str());

	return true;
}

/*!
 * \brief 释放资源
 * 
 * \details 释放YD行情解析器占用的所有资源：
 *          - 断开与服务器的连接
 *          - 清理YD API资源
 *          - 重置所有状态变量
 * 
 * \note 通过调用disconnect()实现资源清理
 */
void ParserYD::release()
{
	disconnect();
}

/*!
 * \brief 建立连接
 * \return 是否连接成功
 * 
 * \details 启动YD行情API连接流程：
 *          - 调用YD API的start()方法开始连接
 *          - 传递this指针作为YDListener回调接口
 *          - 连接成功后会触发notifyReadyForLogin回调
 * 
 * \note 实际的连接建立是异步的，通过回调通知连接状态
 */
bool ParserYD::connect()
{
	if(m_pUserAPI)
	{
		m_pUserAPI->start(this);
	}

	return true;
}

/*!
 * \brief 断开连接
 * \return 是否断开成功
 * 
 * \details 安全断开与YD行情服务器的连接：
 *          - 调用YD API的disconnect()方法断开连接
 *          - 重置API指针为NULL
 *          - 清理连接状态
 * 
 * \note 确保正确断开连接，避免资源泄漏
 */
bool ParserYD::disconnect()
{
	if(m_pUserAPI)
	{
		m_pUserAPI->disconnect();
		m_pUserAPI = NULL;
	}

	return true;
}

/*!
 * \brief 执行登录操作
 * 
 * \details 向YD服务器发起登录请求：
 *          - 使用配置的用户名和密码进行认证
 *          - 调用YD API的login()方法
 *          - 处理登录请求发送失败的情况
 *          - 登录结果通过notifyLogin回调通知
 * 
 * \note 登录是异步操作，结果通过回调函数返回
 * \warning 必须确保用户名和密码正确配置
 */
void ParserYD::DoLogin()
{
	if(m_pUserAPI == NULL)
	{
		return;
	}

	if(!m_pUserAPI->login(m_strUserID.c_str(), m_strPassword.c_str(), NULL, NULL))
	{
		if(m_sink)
			write_log(m_sink, LL_ERROR, "[ParserYD] Sending login request failed");
	}
}

/*!
 * \brief 执行行情数据订阅
 * 
 * \details 向YD服务器发送期货行情订阅请求：
 *          - 检查待订阅合约列表是否为空
 *          - 遍历所有待订阅的期货代码
 *          - 支持带交易所前缀和不带前缀的代码格式
 *          - 通过YD API查找合约信息
 *          - 调用YD API的subscribe()方法订阅行情
 *          - 记录每个合约的订阅结果
 * 
 * \note 如果订阅列表为空则直接返回，支持动态订阅
 * \warning 必须确保合约代码格式正确，否则可能订阅失败
 */
void ParserYD::DoSubscribe()
{
	CodeSet codeFilter = m_filterSubs;
	if(codeFilter.empty())
	{// 如果订阅列表为空，则获取全部合约列表
		return;
	}

	int nCount = 0;
	for(auto& code : codeFilter)
	{
		std::size_t pos = code.find('.');
		const YDInstrument* instInfo = NULL;
		if (pos != std::string::npos)
		{
			instInfo = m_pUserAPI->getInstrumentByID((char*)code.c_str() + pos + 1);
		}
		else
		{
			instInfo = m_pUserAPI->getInstrumentByID((char*)code.c_str());
		}

		if(instInfo == NULL)
			continue;

		bool bRet = m_pUserAPI->subscribe(instInfo);
		write_log(m_sink, LL_ERROR, "[ParserYD] Subscribe md of {} {}", code.c_str(), bRet?"succeed":"failed");
	}
}

/*!
 * \brief 订阅期货行情数据
 * \param vecSymbols 要订阅的期货代码集合
 * 
 * \details 处理期货行情订阅请求：
 *          - 保存订阅列表到m_filterSubs
 *          - 如果未登录，仅保存订阅列表等待登录后处理
 *          - 如果已登录，立即调用DoSubscribe()执行订阅
 *          - 支持动态添加订阅合约
 * 
 * \note 支持在连接前预设订阅列表，连接后自动订阅
 */
void ParserYD::subscribe(const CodeSet &vecSymbols)
{
	if(m_uTradingDate == 0)
	{
		m_filterSubs = vecSymbols;
	}
	else
	{
		m_filterSubs = vecSymbols;
		DoSubscribe();
	}
}

/*!
 * \brief 退订期货行情数据
 * \param vecSymbols 要退订的期货代码集合
 * 
 * \details 处理期货行情退订请求，当前实现为空，可根据需要扩展
 * 
 * \note 预留接口，用于实现行情数据的动态退订功能
 */
void ParserYD::unsubscribe(const CodeSet &vecSymbols)
{
}

/*!
 * \brief 检查连接状态
 * \return true表示已连接，false表示未连接
 * 
 * \details 通过检查YD API指针是否有效来判断连接状态
 * 
 * \note 简单有效的连接状态检查方法
 */
bool ParserYD::isConnected()
{
	return m_pUserAPI!=NULL;
}

/*!
 * \brief 注册行情数据回调接口
 * \param listener 行情数据处理回调接口指针
 * 
 * \details 注册行情数据处理接口，同时获取基础数据管理器：
 *          - 保存回调接口指针用于数据推送
 *          - 获取基础数据管理器用于合约信息查询
 *          - 确保行情数据处理的完整性
 * 
 * \note 基础数据管理器用于验证期货合约信息的有效性
 */
void ParserYD::registerSpi(IParserSpi* listener)
{
	m_sink = listener;

	if(m_sink)
		m_pBaseDataMgr = m_sink->getBaseDataMgr();
}